using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using OrdersManager.Module.Constants;
using OrderWorkflow;
using OrdersManager.Module.Properties;
using Microsoft.Practices.CompositeUI;
using Microsoft.Practices.ObjectBuilder;

namespace OrdersManager.Module.Services
{
	public class OrderWorkflowManager : IOrderWorkflowManager, IDisposable
	{
        private const string _waitingForOrderState = "OrderInitialState";
        private const string _orderOpenState = "OrderOpenedState";
        private const string _orderProcessedState = "OrderProcessedState";
        private const string _orderCompletedState = "OrderShippedState";
        private const string _orderCancelledState = "OrderCancelledState";

        private OrderStateMachineDataExchangeService _orderStateMachine;
        private IOrderWorkflowMappingCollection<Guid,Guid> _orderWorkflowMapping;
		private WorkflowRuntime _workflowRuntime;
        private List<Guid> _pendingEventsInstances = new List<Guid>();

        public OrderWorkflowManager(
            WorkflowRuntime workflowRuntime,
            IOrderWorkflowMappingCollection<Guid,Guid> orderWorkflowMapping)
        {
            _workflowRuntime = workflowRuntime;
            SubscribeToWorkflowEvents();

            _workflowRuntime.StartRuntime();

            ObtainStateMachineInstance();

            _orderWorkflowMapping = orderWorkflowMapping;
        }

        [InjectionConstructor]
        public OrderWorkflowManager([ServiceDependency] IOrderWorkflowMappingCollection<Guid,Guid> orderWorkflowMapping)
            : this(new WorkflowRuntime("OrderWorkflowRuntimeSection"), orderWorkflowMapping)
        {
        }

        ~OrderWorkflowManager()
        {
            Dispose(false);
        }

        private void SubscribeToWorkflowEvents()
        {
            _workflowRuntime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(OnWorkflowTerminated);
            _workflowRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(OnWorkflowIdled);
        }

        private void OnWorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
        {
            FlagOrderAsCancelled(GetWorkflowOrderId(e.WorkflowInstance.InstanceId));
        }

        private void FlagOrderAsCancelled(Guid orderId)
        {
            lock (_orderWorkflowMapping)
            {
                _orderWorkflowMapping.Remove(orderId);
            }
        }

        private void OnWorkflowIdled(object sender, WorkflowEventArgs e)
        {
            SetInstanceCommited(e.WorkflowInstance.InstanceId);
        }

        private void ObtainStateMachineInstance()
        {
            ExternalDataExchangeService dataService = _workflowRuntime.GetService<ExternalDataExchangeService>();

            _orderStateMachine = (OrderStateMachineDataExchangeService)dataService.GetService(typeof(OrderStateMachineDataExchangeService));
        }

        public Guid CreateOrderWorkflow(Guid orderId)
        {
            GuardOrderExistsInWorkflow(orderId);

            Guid instanceId = StartNewWorkflow();

            lock (_orderWorkflowMapping)
            {
                _orderWorkflowMapping.Add(orderId, instanceId);
            }

            return instanceId;
        }

        private Guid StartNewWorkflow()
        {
            WorkflowInstance instance = _workflowRuntime.CreateWorkflow(typeof(OrderStateMachine));
            instance.Start();

            NotifyEventRaised(instance.InstanceId);

            return instance.InstanceId;
        }

        public void NotifyOrderCreated(Guid orderId)
        {
            //TODO: Review with WF team (StateMachine.CurrentStatename return null)
            //GuardTransitionState(orderId, OrderStatus.Opened);

            Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            _orderStateMachine.RaiseOrderCreatedEvent(orderId, instanceId);
            NotifyEventRaised(instanceId);
        }

        public void NotifyOrderUpdated(Guid orderId)
        {
            //TODO: Review with WF team (StateMachine.CurrentStatename return null)
            //GuardTransitionState(orderId, OrderStatus.Opened);
            //GuardUpdateForInitialState(orderId);

            Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            _orderStateMachine.RaiseOrderUpdatedEvent(orderId, instanceId);
            NotifyEventRaised(instanceId);
        }

        public void NotifyOrderProcessed(Guid orderId)
        {
            //TODO: Review with WF team (StateMachine.CurrentStatename return null)
            //GuardTransitionState(orderId, OrderStatus.Processed);

            Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            _orderStateMachine.RaiseOrderProcessedEvent(orderId, instanceId);
            NotifyEventRaised(instanceId);
        }

        public void NotifyOrderShipped(Guid orderId)
        {
            //TODO: Review with WF team (StateMachine.CurrentStatename return null)
            //GuardTransitionState(orderId,OrderStatus.Shipped);

            Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            _orderStateMachine.RaiseOrderShippedEvent(orderId, instanceId);
            NotifyEventRaised(instanceId);
        }

        public void NotifyOrderCancelled(Guid orderId)
        {
            //TODO: Review with WF team (StateMachine.CurrentStatename return null)
            //GuardTransitionState(orderId, OrderStatus.Cancelled);

            Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            _orderStateMachine.RaiseOrderCanceledEvent(orderId, instanceId);
            NotifyEventRaised(instanceId);
        }

        private void NotifyEventRaised(Guid instanceId)
        {
            SetInstanceWaiting(instanceId);
        }

        private void GuardOrderExistsInWorkflow(Guid orderId)
        {
            if (OrderExistsInWorkflow(orderId))
                throw new ArgumentException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.OrderAlreadyExistsMessage,
                        orderId));
        }

        //TODO: Review with WF team (StateMachine.CurrentStatename return null)
        private void GuardTransitionState(Guid orderId, OrderStatus nextState)
        {
            OrderStatus currentState = GetOrderState(orderId);
            ReadOnlyCollection<OrderStatus> possibleStates = GetOrderPossibleStates(orderId);

            if (!possibleStates.Contains(nextState))
                throw new InvalidOperationException(
                    string.Format(CultureInfo.CurrentCulture,
                        Resources.OrderInInvalidStateMessage,
                        currentState,
                        nextState));
        }

        //TODO: Review with WF team (StateMachine.CurrentStatename return null)
        private void GuardUpdateForInitialState(Guid orderId)
        {
            OrderStatus currentState = GetOrderState(orderId);

            if (currentState == OrderStatus.Initial)
                throw new InvalidOperationException(
                    string.Format(CultureInfo.CurrentCulture,
                        Resources.OrderInInvalidStateMessage,
                        currentState,
                        "Update"));
        }

        private bool OrderExistsInWorkflow(Guid orderId)
        {
            return _orderWorkflowMapping.Contains(orderId);
        }

        private Guid GetOrderWorkflowInstanceId(Guid orderId)
        {
            Guid instanceId;
            if (!_orderWorkflowMapping.TryGetValue(orderId, out instanceId))
                throw new KeyNotFoundException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.CantGetWorkflowForOrderIdMessage,
                        orderId.ToString()));

            return instanceId;
        }

        private Guid GetWorkflowOrderId(Guid instanceId)
        {
            foreach (KeyValuePair<Guid, Guid> kvp in _orderWorkflowMapping)
            {
                if (kvp.Value == instanceId)
                {
                    return kvp.Key; //OrderId
                }
            }

            return Guid.Empty;
        }

		public ReadOnlyCollection<OrderStatus> GetOrderPossibleStates(Guid orderId)
		{
			List<OrderStatus> possibleStates = new List<OrderStatus>();            

            if (!OrderExistsInWorkflow(orderId))
            {
                possibleStates.Add(OrderStatus.Initial);
            }
            else
            {
                Guid instanceId = GetOrderWorkflowInstanceId(orderId);
                StateMachineWorkflowInstance stateMachine = new StateMachineWorkflowInstance(_workflowRuntime, instanceId);

                foreach (string stateName in stateMachine.PossibleStateTransitions)
                {
                    possibleStates.Add(StateName2StateEnum(stateName));
                }

            }
			
			return possibleStates.AsReadOnly();
		}

		public ReadOnlyCollection<Guid> GetOrdersByState(OrderStatus stateMode)
		{
			List<Guid> orders = new List<Guid>();

			lock (_orderWorkflowMapping)
			{
				foreach (KeyValuePair<Guid,Guid> kvp in _orderWorkflowMapping)
				{
                    Guid orderId = kvp.Key;
                    Guid instanceId = kvp.Value;

                    if (!InstanceIsWaiting(instanceId)
                        && GetOrderState(orderId) == stateMode)
                    {
                        orders.Add(orderId);
                    }
				}
			}

			return orders.AsReadOnly();
		}

		public OrderStatus GetOrderState(Guid orderId)
		{
			Guid instanceId = GetOrderWorkflowInstanceId(orderId);
            StateMachineWorkflowInstance stateMachine = 
                new StateMachineWorkflowInstance(_workflowRuntime, instanceId);

            string currentStateName = stateMachine.CurrentStateName;

            return StateName2StateEnum(currentStateName);
		}

        public bool InstanceIsWaiting(Guid instanceId)
        {
            return _pendingEventsInstances.Contains(instanceId);
        }

        private void SetInstanceWaiting(Guid instanceId)
        {
            if (!InstanceIsWaiting(instanceId))
            {
                _pendingEventsInstances.Add(instanceId);
            }
        }

        private void SetInstanceCommited(Guid instanceId)
        {
            if (InstanceIsWaiting(instanceId))
            {
                _pendingEventsInstances.Remove(instanceId);
            }
        }
        
        private static OrderStatus StateName2StateEnum(string stateName)
        {
            switch (stateName)
            {
                case _waitingForOrderState: return OrderStatus.Initial;
                case _orderOpenState: return OrderStatus.Opened;
                case _orderProcessedState: return OrderStatus.Processed;
                case _orderCompletedState: return OrderStatus.Shipped;
                case _orderCancelledState: return OrderStatus.Cancelled;
            }

            throw new ArgumentException(
                string.Format(
                    CultureInfo.CurrentCulture,
                    Resources.InvalidStateNameMessage,
                    stateName));
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_workflowRuntime != null)
                {
                    _workflowRuntime.Dispose();
                }
            }
        }

    }

}